/**
 * File: ContigInfo.java
 * Created by: mhaimel
 * Created on: Mar 5, 2010
 * CVS:  $Id: ContigInfo.java 1.0 Mar 5, 2010 2:38:24 PM mhaimel Exp $
 */
package uk.ac.ebi.curtain.model.graph.curtain;

import static uk.ac.ebi.curtain.utils.ArrayHelper.getInitalArray;
import static uk.ac.ebi.curtain.utils.ArrayHelper.toArray;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicIntegerArray;

import org.apache.commons.lang.StringUtils;

import uk.ac.ebi.curtain.model.graph.NodeEdge;
import uk.ac.ebi.curtain.utils.BitUtil;
import uk.ac.ebi.curtain.utils.concurrent.CurtainAtomicIntegerArray;
import uk.ac.ebi.curtain.utils.concurrent.CurtainAtomicLongArray;

/**
 * @author mhaimel
 *
 */
public class ContigInfo {

	public static final int PARTIAL_START = (int)Math.pow(2, 0);
	public static final int PARTIAL_END = (int)Math.pow(2, 1);
	
	private final Long id;
	private final Long length;
	private final AtomicIntegerArray flag;
	private final CurtainAtomicLongArray coverageBpCount;
	private final CurtainAtomicIntegerArray coverageSequCount;
	private final CurtainAtomicIntegerArray partialStart;
	private final CurtainAtomicIntegerArray partialEnd;
	private final CurtainAtomicIntegerArray full;

	public ContigInfo(Long id, Long length, Integer categories) {
		this(id,
			length,
			categories,
			getInitalArray(categories, 0),//partialStart
			getInitalArray(categories, 0),//partialEnd
			getInitalArray(categories, 0),//full
			getInitalArray(categories, 0),//sequCount
			getInitalArray(categories, 0l)//covCount
		);
	}
	
	@Override
	public String toString() {
		return StringUtils.join(new Object[]{
				"id="+id,
				"length="+length,
				"bpCnt="+coverageBpCount,
				"sequCnt="+coverageSequCount,
				"partialStart="+partialStart,
				"partialEnd="+partialEnd,
				"full="+full
		},',');
	}
	
	public ContigInfo(Long id, Long length, Integer categories, Integer[] partialStart,Integer[] partialEnd, Integer[] full, Integer[] sequCount, Long[] bpCount) {
		super();
		this.id = id;
		this.length = length;
		this.partialStart = new CurtainAtomicIntegerArray(partialStart);
		this.partialEnd = new CurtainAtomicIntegerArray(partialEnd);
		this.full = new CurtainAtomicIntegerArray(full);
		this.coverageSequCount = new CurtainAtomicIntegerArray(sequCount);
		this.coverageBpCount = new CurtainAtomicLongArray(bpCount);
		int[] arr = new int[categories];
		Arrays.fill(arr, 0);
		this.flag = new AtomicIntegerArray(arr);
	}
	
	public Integer getCategories(){
		return this.coverageBpCount.length();
	}
	
	public void addSequence(Integer cat, Long length){
		incrementBpCount(cat, length);
		incrementSequenceCount(cat);
	}
	
	/**
	 * Contig x times covered by bases 
	 * @param cat
	 * @return
	 */
	public Double calculateSequenceCoverage(Integer cat){
		Long seqLen = this.getLength();
		if(seqLen <= 0){
			return 0d;
		}
		return getCoverageBpCount(cat).doubleValue() / seqLen;
	}
	
	private CurtainAtomicIntegerArray _partialArray(NodeEdge edge){
		if(NodeEdge.Start.equals(edge)){
			return partialStart;
		} else if(NodeEdge.End.equals(edge)){
			return partialEnd;
		}
		throw new IllegalArgumentException("Unkown edge: " + edge);
	}
	
	public Integer incrementPartial(Integer cat, NodeEdge edge) {
		return _partialArray(edge).incrementAndGet(cat);
	}
	
	public Integer incrementFull(Integer cat) {
		return full.incrementAndGet(cat);
	}
	
	private Long incrementBpCount(Integer cat, Long count) {
		return coverageBpCount.addAndGet(cat, count);
	}
	
	private Integer incrementSequenceCount(Integer cat) {
		return coverageSequCount.incrementAndGet(cat);
	}

	/**
	 * @return the id
	 */
	public Long getId() {
		return id;
	}

	/**
	 * @return the length
	 */
	public Long getLength() {
		return length;
	}

	
	public double getAvgReadLength(Integer cat){
		Integer cnt = getCoverageSequCount(cat);
		cnt += getFull(cat);
		if(cnt > 0){
			return getCoverageBpCount(cat)/cnt;
		} 
		return 0d;
	}

	/**
	 * @param cat Category
	 * @return the coverageBpCount
	 */
	public Long getCoverageBpCount(Integer cat) {
		return coverageBpCount.get(cat);
	}

	/**
	 * @param cat Category
	 * @return the coverageSequCount
	 */
	public Integer getCoverageSequCount(Integer cat) {
		return coverageSequCount.get(cat);
	}

	/**
	 * @param cat Category
	 * @return the partial
	 */
	public Integer getPartial(Integer cat, NodeEdge edge) {		
		return _partialArray(edge).get(cat);
	}

	/**
	 * @param cat Category
	 * @return the full
	 */
	public Integer getFull(Integer cat) {
		return full.get(cat);
	}		
	
	public Integer[] arrayOfFull(){
		return toArray(full);
	}
	
	public Integer[] arrayOfPartial(NodeEdge edge){
		return toArray(_partialArray(edge));
	}
	
	public Long[] arrayOfBpCountCov(){
		return toArray(coverageBpCount);
	}
	
	public Integer[] arrayOfSequenceCountCov(){
		return toArray(coverageSequCount);
	}	
	
	public Integer[] arrayOfSequenceCoverage(){
		int len = this.coverageSequCount.length();
		Integer[] arr = (Integer[]) Array.newInstance(Integer.class, len);
		for(int i = 0; i < len; ++i){
			arr[i] = calculateSequenceCoverage(i).intValue();
		}
		return arr;
	}
	
	public void removePartialFlag(int cat, NodeEdge edge){
		int flag = _partialFlag(edge);
		removeFlag(cat,flag);
	}

	private int _partialFlag(NodeEdge edge) {
		int flag = PARTIAL_START;
		if(NodeEdge.End.equals(edge)){
			flag = PARTIAL_END;
		}
		return flag;
	}
	
	public void setPartialFlag(int cat, NodeEdge edge){
		int flag = _partialFlag(edge);
		setFlag(cat,flag);
	}

	public boolean hasPartialFlag(int cat, NodeEdge edge) {
		return hasFlag(cat, _partialFlag(edge));
	}

	public boolean hasFlag(int cat, int flag) {
		return BitUtil.hasFlag(this.flag.get(cat), flag);
	}

	public void removeFlag(int cat, int flag) {
		do{
			int f = this.flag.get(cat);
			if(BitUtil.hasFlag(f, flag)){
				if(this.flag.compareAndSet(cat, f, BitUtil.flipFlag(f, flag))){
					return;
				}
			} else {
				return;
			}
		} while(true);
	}

	public void setFlag(int cat, int flag) {
		int f;
		do {
			f = this.flag.get(cat);
		}while (!this.flag.compareAndSet(cat, f, BitUtil.setFlag(f, flag)));
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		result = prime * result + ((length == null) ? 0 : length.hashCode());
		result = prime * result + Arrays.hashCode(arrayOfPartial(NodeEdge.Start));
		result = prime * result + Arrays.hashCode(arrayOfPartial(NodeEdge.End));
		result = prime * result + Arrays.hashCode(arrayOfFull());
		result = prime * result + Arrays.hashCode(arrayOfSequenceCountCov());
		result = prime * result + Arrays.hashCode(arrayOfBpCountCov());
		return result;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		ContigInfo other = (ContigInfo) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		if (length == null) {
			if (other.length != null)
				return false;
		} else if (!length.equals(other.length))
			return false;
		if (!Arrays.equals(arrayOfPartial(NodeEdge.Start), other.arrayOfPartial(NodeEdge.Start)))
			return false;
		if (!Arrays.equals(arrayOfPartial(NodeEdge.End), other.arrayOfPartial(NodeEdge.End)))
			return false;
		if (!Arrays.equals(arrayOfFull(), other.arrayOfFull()))
			return false;
		if (!Arrays.equals(arrayOfBpCountCov(), other.arrayOfBpCountCov()))
			return false;
		if (!Arrays.equals(arrayOfSequenceCountCov(), other.arrayOfSequenceCountCov()))
			return false;
		return true;
	}
	
}
